﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using BrainTechLLC;
using System.Windows.Threading;
using System.Windows;
using BrainTechLLC.ThreadSafeObjects;
using System.Threading;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization;
using System.Linq.Expressions;

//namespace BrainTechLLC.NotifyPropertyChanged
namespace BrainTechLLC
{
    [Serializable]
    [DataContract]
    public partial class BaseNotifyPropertyChanged : INotifyPropertyChanged
    {

#if SILVERLIGHT
        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;
#else
        [NonSerialized]
        public static readonly DispatcherPriority _priority = DispatcherPriority.DataBind; // was Normal

        [NonSerialized]
        public FastSmartWeakEvent<PropertyChangedEventHandler> _propertyChanged = new FastSmartWeakEvent<PropertyChangedEventHandler>();

        /// <summary>
        /// The property changed event handler
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                if (_propertyChanged == null)
                {
                    Interlocked.CompareExchange<FastSmartWeakEvent<PropertyChangedEventHandler>>(ref _propertyChanged, new FastSmartWeakEvent<PropertyChangedEventHandler>(), null);
                }

                _propertyChanged.Add(value);
            }
            remove { _propertyChanged.Remove(value); }
        }
#endif

        /// <summary>
        /// Only active in DEBUG, verifies that the propertyName exists on this type
        /// </summary>
        /// <param name="propertyName"></param>
        [Conditional("DEBUG")]
        private void VerifyProperty(string propertyName)
        {
            Type type = this.GetType();

            // Look for a public property with the specified name.
            PropertyInfo propInfo = type.GetProperty(propertyName);

            if (propInfo == null)
            {
                string msg = string.Format("{0} is not a public property of {1}", propertyName, type.FullName);
                Debug.Assert(false, msg);
            }
        }

        /// <summary>
        /// Enters lock / critical region
        /// </summary>
        /// <param name="o">lock object</param>
        /// <param name="ms">number of milliseconds to wait if lock is busy</param>
        public void EnterMonitor(object o, int ms)
        {
            bool lockTaken = false;
            while (!lockTaken)
            {
#if SILVERLIGHT
                if (!Monitor.TryEnter(o, ms))
                {
                    lockTaken = false;
                }
                else
                {
                    lockTaken = true;
                }
#else
                Monitor.TryEnter(o, ms, ref lockTaken);
#endif
                if (!lockTaken)
                {
                    Console.WriteLine("Sleeping 20ms");
                    Thread.Sleep(20);
                }
            }
        }

        /// <summary>
        /// Raise the property changed event
        /// </summary>
        /// <param name="propertyName"></param>
        public void RaisePropertyChanged(string propertyName)
        {
            OnPropertyChanged(propertyName);
        }

        //public bool ConvenientSet<T>(ref T val, T newVal, string propertyName)
        //{
        //    if ((newVal == null || !newVal.Equals(val)) || (val == null || !val.Equals(newVal)))
        //    {
        //        val = newVal;
        //        OnPropertyChanged(propertyName);
        //        return true;
        //    }

        //    return false;
        //}

        /// <summary>
        /// Protected method used to raise property changed event, optionally bypassing event suspended check
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="checkSuspend"></param>
        protected virtual void OnPropertyChanged(string propertyName, bool checkSuspend)
        {
            if (checkSuspend)
            {
                int suspend = GetSuspendPropertyChanged();

                if (suspend > 0)
                {
                    AddPropertyChanged(this, propertyName);
                    return;
                }
            }

#if SILVERLIGHT
#else
            if (_propertyChanged == null)
            {
                Interlocked.CompareExchange<FastSmartWeakEvent<PropertyChangedEventHandler>>(ref _propertyChanged, new FastSmartWeakEvent<PropertyChangedEventHandler>(), null);
            }
#endif

            // HEREHERE
            // VerifyProperty(propertyName);

            try
            {
                BeforePropertyChanged(propertyName);
                var eventArgs = new PropertyChangedEventArgs(propertyName);
#if SILVERLIGHT
                var evt = PropertyChanged;
                if (evt != null)
                {
                    evt(this, eventArgs);
                }
#else
                _propertyChanged.Raise(this, eventArgs);
#endif
                AfterPropertyChanged(propertyName);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        /// <summary>
        /// Protected method used to raise multiple property changed events, optionally bypassing event suspended check
        /// </summary>
        /// <param name="checkSuspend"></param>
        /// <param name="propertyNames"></param>
        protected void OnPropertyChanged(bool checkSuspend, params string[] propertyNames)
        {
            if (checkSuspend)
            {
                int suspend = GetSuspendPropertyChanged();

                if (suspend > 0)
                {
                    ReadPropertyChanges(propertyNames);
                    return;
                }
            }

#if SILVERLIGHT
#else
            if (_propertyChanged == null)
            {
                Interlocked.CompareExchange<FastSmartWeakEvent<PropertyChangedEventHandler>>(ref _propertyChanged, new FastSmartWeakEvent<PropertyChangedEventHandler>(), null);
            }
#endif

            for (int n = 0; n < propertyNames.Length; n++)
            {
                BeforePropertyChanged(propertyNames[n]);
                var eventArgs = new PropertyChangedEventArgs(propertyNames[n]);
#if SILVERLIGHT
                var evt = PropertyChanged;
                if (evt != null)
                {
                    evt(this, eventArgs);
                }
#else
                _propertyChanged.Raise(this, eventArgs);
#endif
                AfterPropertyChanged(propertyNames[n]);
            }
        }

        protected virtual void ReadPropertyChanges(string[] propertyNames)
        {
            var collection = GetPropertyChanges();
            foreach (var propertyName in propertyNames)
            {
                collection.AddPropertyChange(new ClassAndPropertyChange(this, propertyName));
            }
        }

        /// <summary>
        /// Protected method used to raise property changed event
        /// </summary>
        /// <param name="propertyName"></param>
        protected void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(propertyName, true);
        }

        /// <summary>
        /// Protected method used to raise multiple property changed events
        /// </summary>
        /// <param name="propertyNames"></param>
        protected void OnPropertyChanged(params string[] propertyNames)
        {
            OnPropertyChanged(true, propertyNames);
        }

        /// <summary>
        /// Override to execute code before property changed event is fired
        /// </summary>
        /// <param name="propertyName"></param>
        protected virtual void BeforePropertyChanged(string propertyName)
        {
        }

        /// <summary>
        /// Override to execute code after property changed event is fired
        /// </summary>
        /// <param name="propertyName"></param>
        protected virtual void AfterPropertyChanged(string propertyName)
        {
        }

#if SILVERLIGHT
        public void Set<T>(Expression<Func<T>> expression, T value)
            where T : class
        {
            MemberExpression binding = expression.Body as MemberExpression;
            FieldInfo m = binding.Member as FieldInfo;
            T oldValue = m.GetValue(this) as T;
            if (oldValue != value)
            {
                m.SetValue(this, value);
                OnPropertyChanged(m.Name.Substring(1));
                OnPropertyChanged("");
            }
        }

        public void SetValue<T>(Expression<Func<T>> expression, T value)
            where T : IComparable<T>
        {
            MemberExpression binding = expression.Body as MemberExpression;
            FieldInfo m = binding.Member as FieldInfo;
            T oldValue = (T)m.GetValue(this);
            if (!oldValue.Equals(value))
            {
                m.SetValue(this, value);
                OnPropertyChanged(m.Name.Substring(1));
                OnPropertyChanged("");
            }
        }
#else
        public void Set<T>(Expression<Func<T>> expression, T value)
            where T : class
        {
            MemberExpression binding = expression.Body as MemberExpression;
            FieldInfo m = binding.Member as FieldInfo;
            T oldValue = m.GetValue(this) as T;
            if (oldValue != value)
            {
                m.SetValue(this, value);
                OnPropertyChanged(m.Name.Substring(1));
            }
        }

        public void SetValue<T>(Expression<Func<T>> expression, T value)
            where T : IComparable<T>
        {
            MemberExpression binding = expression.Body as MemberExpression;
            FieldInfo m = binding.Member as FieldInfo;
            T oldValue = (T)m.GetValue(this);
            if (!oldValue.Equals(value))
            {
                m.SetValue(this, value);
                OnPropertyChanged(m.Name.Substring(1));
            }
        }
#endif
    }

    public partial class BaseNotifyPropertyChanged : INotifyPropertyChanged
    {
        [NonSerialized]
        public static readonly ThreadSafeLookupNonRef<int, int> SuspendPropertyChangedEvent = new ThreadSafeLookupNonRef<int, int>();

        //[NonSerialized]
        //public static readonly ThreadSafeLookupNonRef<int, int> CancelPropertyChangedEvent = new ThreadSafeLookupNonRef<int, int>();

        [NonSerialized]
        public static readonly ThreadSafeLookup<int, ClassAndPropertyChangeCollection> PropertyChangesThatOccurred = new ThreadSafeLookup<int, ClassAndPropertyChangeCollection>();

        [NonSerialized]
        public static Dispatcher _dispatcher;

        [NonSerialized]
        public static bool _gotDispatcher;

        [NonSerialized]
        public static object _lock = new object();

        public virtual void AddPropertyChanged(BaseNotifyPropertyChanged classObject, string propertyName)
        {
            var collection = GetPropertyChanges();
            ClassAndPropertyChange p = new ClassAndPropertyChange(classObject, propertyName);
            collection.AddPropertyChange(p);
        }

        public static ClassAndPropertyChangeCollection TryGetPropertyChanges()
        {
            int key = Thread.CurrentThread.ManagedThreadId;
            ClassAndPropertyChangeCollection result;

            if (!PropertyChangesThatOccurred.TryGetValue(key, out result))
            {
                return null;
            }

            return result;
        }

        public static ClassAndPropertyChangeCollection GetPropertyChanges()
        {
            int key = Thread.CurrentThread.ManagedThreadId;
            ClassAndPropertyChangeCollection result;

            if (!PropertyChangesThatOccurred.TryGetValue(key, out result))
            {
                result = new ClassAndPropertyChangeCollection();
                PropertyChangesThatOccurred.Add(key, result);
            }

            return result;
        }

        public static bool SetSuspendPropertyChanged(bool on, bool runAllIfReEnable)
        {
            if (SetSuspendPropertyChanged(on))
            {
                if (runAllIfReEnable)
                {
                    var changes = TryGetPropertyChanges();

                    if (changes != null)
                    {
                        changes.RunAllPropertyChanges();
                    }
                }

                return true;
            }

            return false;
        }

        public static bool SetSuspendPropertyChanged(bool on)
        {
            int key = Thread.CurrentThread.ManagedThreadId;

            if (on)
            {
                int n = SuspendPropertyChangedEvent[key];

                if (n == 0)
                {
                    ClassAndPropertyChangeCollection changes = TryGetPropertyChanges();

                    if (changes != null)
                    {
                        changes.PropertyChanges.Clear();
                    }
                }

                SuspendPropertyChangedEvent[key] = n + 1;
                return false;
            }
            else
            {
                int n = SuspendPropertyChangedEvent[key];

                if (n == 1)
                {
                    SuspendPropertyChangedEvent.Remove(key);
                    return true;
                }
                else
                {
                    SuspendPropertyChangedEvent[key] = n - 1;
                    return false;
                }
            }
        }

        //public static bool SetNoPropertyChanges(bool on)
        //{
        //    int key = Thread.CurrentThread.ManagedThreadId;

        //    if (on)
        //    {
        //        int n = CancelPropertyChangedEvent[key];

        //        if (n == 0)
        //        {                    
        //        }

        //        CancelPropertyChangedEvent[key] = n + 1;
        //        return false;
        //    }
        //    else
        //    {
        //        int n = CancelPropertyChangedEvent[key];

        //        if (n == 1)
        //        {
        //            CancelPropertyChangedEvent.Remove(key);
        //            return true;
        //        }
        //        else
        //        {
        //            CancelPropertyChangedEvent[key] = n - 1;
        //            return false;
        //        }
        //    }
        //}

        public static int GetSuspendPropertyChanged()
        {
            int result;
            int key = Thread.CurrentThread.ManagedThreadId;

            if (!SuspendPropertyChangedEvent.TryGetValue(key, out result))
            {
                result = 0;
            }

            return result;
        }

        //public static int GetNoPropertyChanged()
        //{
        //    int result;
        //    int key = Thread.CurrentThread.ManagedThreadId;

        //    if (!CancelPropertyChangedEvent.TryGetValue(key, out result))
        //    {
        //        result = 0;
        //    }

        //    return result;
        //}
    }

    [Serializable]
    [DataContract]
    public class BaseTrackModified : BaseNotifyPropertyChanged
    {
        [DataMember]
        public bool _modified;
        [DataMember]
        public bool _attached;

        public BaseTrackModified()
        {
            AttachModifiedTracker();
        }

        protected void AttachModifiedTracker()
        {
            this.PropertyChanged += new PropertyChangedEventHandler(BaseTrackModified_PropertyChanged);
            _attached = true;
        }

        private void BaseTrackModified_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName != "Modified")
            {
                Modified = true;
            }
        }

        public bool Modified
        {
            get { return _modified; }
            set
            {
                if (_modified != value)
                {
                    _modified = value;
                    OnPropertyChanged("Modified");
                }
            }
        }
    }

    [Serializable]
    [DataContract]
    public class ClassAndPropertyChange
    {
        [DataMember]
        public string PropertyName;
        [DataMember]
        public BaseNotifyPropertyChanged ClassObject;

        public virtual bool EqualsOther(ClassAndPropertyChange other)
        {
            return (PropertyName == other.PropertyName) && (ClassObject == other.ClassObject);
        }

        public ClassAndPropertyChange(BaseNotifyPropertyChanged classObject, string propertyName)
        {
            ClassObject = classObject;
            PropertyName = propertyName;
        }

        public virtual void RaisePropertyChanged()
        {
#if SILVERLIGHT
            ClassObject.RaisePropertyChanged(PropertyName);         
#else
            if (ClassObject._propertyChanged != null && ClassObject._propertyChanged.eventEntries.Count > 0)
            {
                ClassObject.RaisePropertyChanged(PropertyName);
            }
#endif
        }
    }

    [Serializable]
    [DataContract]
    public class ClassAndPropertyChangeCollection
    {
        [DataMember]
        // candidate for conversion to dictionary lookup by property name if list is frequently large
        public ThreadSafeListAndLookup<object, ClassAndPropertyChange> PropertyChanges = new ThreadSafeListAndLookup<object, ClassAndPropertyChange>();

        public void AddPropertyChange(ClassAndPropertyChange propChange)
        {
            var list = PropertyChanges[propChange.ClassObject];
            if (list != null)
            {
                for (int n = list.Count - 1; n >= 0; n--)
                {
                    var p = list[n];

                    if (propChange.EqualsOther(p))
                        return;
                }
            }
            else
            {
            }

            PropertyChanges.Add(propChange.ClassObject, propChange);
        }

        public void RunAllPropertyChanges()
        {
            var all = PropertyChanges.AllItems;

            for (int n = 0; n < all.Count; n++)
            {
                var p = all[n];
                p.RaisePropertyChanged();
            }

            PropertyChanges.Clear();
        }
    }

}
